#!/usr/bin/python
# -*- coding: utf-8 -*-
# require python 3.6 or upper
#
# Copyright 2019 cerulany <www.zhihu.com/people/cerulany>
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

"""
    author: Cerulany
    last edited: 210127-173342

REVISIONS:
    210126 Pioneer600的程testのsysinfo add uptime
    Pioneer600的程runのmenu add assign leafs to simplify editing

    210122 Pioneer600的程testのsysinfo

    210121 Pioneer600的程runのmenu
    Pioneer600的程testのmenu
    Pioneer600的程runのmenu add exit in standalone mode

    210119 Pioneer600的程doのcheckKey
    Pioneer600的程servのbmp280

    210118 Pioneer600的程doのbeep
    Pioneer600的程doのgetKey
"""
import time
from alib import alib
M=alib()
K=alib() # just a dummy

def terminal的程doのwarning(args):
    #print bold yellow text on terminal

    txt=args

    import termcolor as tc

    print(tc.colored(txt, 'yellow', 'on_grey', ['bold']))    
    return None if 'ans' not in locals() else ans
    

def OS的程getのosType(args):
    #get the type of current operating system
    #the value of environ.OS

    import os
    if 'OS' in os.environ:
        ans=os.environ['OS']
    else:
        ans='Linux'

    print('The current operating system is: %s'%ans)    
    return None if 'ans' not in locals() else ans
    

def wiTimeStr的程getのyS(args):
    from datetime import datetime

    if 'args' not in locals() or args==None:
        args=datetime.now()

    ans=args.strftime('%y%m%d-%H%M%S')    
    return None if 'ans' not in locals() else ans
    

def sandbox的程getのdir(args):
    #get the sandbox directory for current OS
    #if args, returns the file in sandbox

    osType=OS的程getのosType(None)

    locDict=K.GetApp('sandbox').getValue('#locations')
    if osType == 'Linux':
        import os
        ans=os.path.expanduser('~/Sandbox')
        print('Sandbox location: %s'%ans)
    elif osType in locDict:
        ans=locDict[osType]
        print('Sandbox location: %s'%ans)
    else:
        ans=None
        print('Warning the current OS(%s) does not have a specified sandbox location.'%osType)

    import os

    if 'args' in locals() and args:
        ans=os.path.join(ans,args)    
    return None if 'ans' not in locals() else ans
    

def terminal的程doのrun(args):
    #run system command and return the output

    #call the cmd and get output
    #from https://linuxhandbook.com/execute-shell-command-python/

    largs=args #list of command arguments

    import subprocess
    mo=subprocess.Popen(largs,shell=True,
    stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    so, se = mo.communicate()
    if OS的程getのosType(None)=='Windows_NT':
        code='gbk'
    else:
        code='utf8'

    so=so.decode(code)

    if se is not None:
        se=se.decode(code)
        terminal的程doのwarning(se)

    ans=so    
    return None if 'ans' not in locals() else ans
    

def maop空path的程getのexpand(args):
    #expand maop path variable in input path, for example {WorkDir}/minion.jpg

    path=args
    paths=K.GetApp('maop path')['#lib']

    ans=args.format(**paths)    
    return None if 'ans' not in locals() else ans
    

def M的程doのset(args):
    #set variable

    name, value = args

    M.setValue('%s'%name,value)

    print('variable {%s} set in M'%name)    
    return None if 'ans' not in locals() else ans
    

def terminal的程doのemphasize(args):
    #print bold cyan text on terminal

    txt=args

    import termcolor as tc

    print(tc.colored(txt, 'cyan', 'on_grey', ['bold']))    
    return None if 'ans' not in locals() else ans
    

def sandbox的程getのnewFile(args):
    #get the full path of a new file with a time-based filename
    #append args to the file

    import os

    ans=sandbox的程getのdir(None)

    fn=wiTimeStr的程getのyS(None)

    ans=os.path.join(ans,fn)

    if args:
        ans+=args    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程servのadda(args):
    # setup adda server

    import smbus
    import time
    bus = smbus.SMBus(1)

    serv=alib('',bus=bus)

    serv['address'] = 0x48
    serv['cmd'] = 0x40
    serv['A0'] = 0x40
    serv['A1'] = 0x41
    serv['A2'] = 0xA2
    serv['A3'] = 0xA3

    def Ad(add,serv=serv):
        serv.bus.write_byte(serv.address,serv[f'A{add}'])
        v=serv.bus.read_byte(serv.address)
        return v*3.3/255

    def Da(v,serv=serv):
        serv.bus.write_byte_data(serv.address,serv.cmd,v)


    serv['Ad']=Ad
    serv['Da']=Da

    M的程doのset(('serv|adda',serv))

    ans=serv    
    return None if 'ans' not in locals() else ans
    

def WalArt空minion的程getのminion(args):
    # get a new minion

    opt=args #[name][beQuiet][standBy]

    if 'beQuiet' not in opt:
        opt['beQuiet']=False
    if 'standBy' not in opt:
        opt['standBy']=False

    try:
        from WalArt.waTool import minion
    except:
        from minion import minion

    ans=minion(opt.name,BeQuiet=opt.beQuiet)

    ans.standby=opt.standBy

    M的程doのset((f'aMinion|{opt.name}',ans))    
    return None if 'ans' not in locals() else ans
    

def affine空transformation的程getのkbtrans(args):
    # transform the section into another section (a1,a2) to (b1,b2)
    # return the transforming function

    opt=args #[a1][a2][b1][b2]

    k=(opt.b2-opt.b1)/(opt.a2-opt.a1)
    a1=opt.a1
    b1=opt.b1

    def T(x,k=k,a1=a1,b1=b1):
        return k*(x-a1)+b1

    ans=T #return the Transformation    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程servのbmp280(args):
    #initialize TP sensor on the board
    # from waveshare wiki
    import time
    import smbus

    BMP280_I2C_ADDRESS       =    0x76          #SDO = 0

    class BMP180(object):
        def __init__(self, address=0x76):
            self._address = address
            import smbus
            self._bus = smbus.SMBus(1)
            # BMP280 address.
            self.BMP280_I2C_ADDRESS       =    0x76          #SDO = 0

            #Registers  value
            self.BMP280_ID_Value          =    0x58
            self.BMP280_RESET_VALUE       =    0xB6

            # BMP280 Registers definition  
            self.BMP280_TEMP_XLSB_REG     =    0xFC          #Temperature XLSB Register 
            self.BMP280_TEMP_LSB_REG      =    0xFB         #Temperature LSB Register  
            self.BMP280_TEMP_MSB_REG      =    0xFA         #Temperature LSB Register   
            self.BMP280_PRESS_XLSB_REG    =    0xF9          #Pressure XLSB  Register 
            self.BMP280_PRESS_LSB_REG     =    0xF8          #Pressure LSB Register 
            self.BMP280_PRESS_MSB_REG     =    0xF7          #Pressure MSB Register                         
            self.BMP280_CONFIG_REG        =    0xF5         #Configuration Register 
            self.BMP280_CTRL_MEAS_REG     =    0xF4         #Ctrl Measure Register  
            self.BMP280_STATUS_REG        =    0xF3         #Status Register 
            self.BMP280_RESET_REG         =    0xE0          #Softreset Register  
            self.BMP280_ID_REG            =    0xD0         #Chip ID Register 

            #calibration parameters   
            self.BMP280_DIG_T1_LSB_REG    =    0x88  
            self.BMP280_DIG_T1_MSB_REG    =    0x89  
            self.BMP280_DIG_T2_LSB_REG    =    0x8A  
            self.BMP280_DIG_T2_MSB_REG    =    0x8B  
            self.BMP280_DIG_T3_LSB_REG    =    0x8C  
            self.BMP280_DIG_T3_MSB_REG    =    0x8D  
            self.BMP280_DIG_P1_LSB_REG    =    0x8E  
            self.BMP280_DIG_P1_MSB_REG    =    0x8F  
            self.BMP280_DIG_P2_LSB_REG    =    0x90  
            self.BMP280_DIG_P2_MSB_REG    =    0x91  
            self.BMP280_DIG_P3_LSB_REG    =    0x92  
            self.BMP280_DIG_P3_MSB_REG    =    0x93  
            self.BMP280_DIG_P4_LSB_REG    =    0x94  
            self.BMP280_DIG_P4_MSB_REG    =    0x95  
            self.BMP280_DIG_P5_LSB_REG    =    0x96  
            self.BMP280_DIG_P5_MSB_REG    =    0x97  
            self.BMP280_DIG_P6_LSB_REG    =    0x98  
            self.BMP280_DIG_P6_MSB_REG    =    0x99  
            self.BMP280_DIG_P7_LSB_REG    =    0x9A  
            self.BMP280_DIG_P7_MSB_REG    =    0x9B  
            self.BMP280_DIG_P8_LSB_REG    =    0x9C  
            self.BMP280_DIG_P8_MSB_REG    =    0x9D  
            self.BMP280_DIG_P9_LSB_REG    =    0x9E  
            self.BMP280_DIG_P9_MSB_REG    =    0x9F 
            # Load calibration values.
            if self._read_byte(self.BMP280_ID_REG) == self.BMP280_ID_Value:
                self._load_calibration()
                ctrlmeas = 0xFF #BMP280_T_MODE_1 << 5 | BMP280_P_MODE_1 << 2 | BMP280_SLEEP_MODE;  
                config = 0x14   #BMP280_T_SB1 << 5 | BMP280_FILTER_MODE_1 << 2;
                self._write_byte(self.BMP280_CTRL_MEAS_REG, ctrlmeas); 
                self._write_byte(self.BMP280_CONFIG_REG, config)
            else:
                print("Read BMP280 id error!\r\n")
        def _read_byte(self,cmd):
            return self._bus.read_byte_data(self._address,cmd)
    
        def _read_u16(self,cmd):
            LSB = self._bus.read_byte_data(self._address,cmd)
            MSB = self._bus.read_byte_data(self._address,cmd+1)
            return (MSB    << 8) + LSB

        def _read_s16(self,cmd):
            result = self._read_u16(cmd)
            if result > 32767:result -= 65536
            return result

        def _write_byte(self,cmd,val):
            self._bus.write_byte_data(self._address,cmd,val)

        def _load_calibration(self):
            "load calibration"
        
            """ read the temperature calibration parameters """
            self.dig_T1 =self._read_u16(self.BMP280_DIG_T1_LSB_REG)
            self.dig_T2 =self._read_s16(self.BMP280_DIG_T2_LSB_REG)
            self.dig_T3 =self._read_s16(self.BMP280_DIG_T3_LSB_REG)
            """ read the pressure calibration parameters """
            self.dig_P1 =self._read_u16(self.BMP280_DIG_P1_LSB_REG)
            self.dig_P2 =self._read_s16(self.BMP280_DIG_P2_LSB_REG)
            self.dig_P3 =self._read_s16(self.BMP280_DIG_P3_LSB_REG)
            self.dig_P4 =self._read_s16(self.BMP280_DIG_P4_LSB_REG)
            self.dig_P5 =self._read_s16(self.BMP280_DIG_P5_LSB_REG)
            self.dig_P6 =self._read_s16(self.BMP280_DIG_P6_LSB_REG)
            self.dig_P7 =self._read_s16(self.BMP280_DIG_P7_LSB_REG)
            self.dig_P8 =self._read_s16(self.BMP280_DIG_P8_LSB_REG)
            self.dig_P9 =self._read_s16(self.BMP280_DIG_P9_LSB_REG)
        
        def compensate_temperature(self,adc_T):
            """Returns temperature in DegC, double precision. Output value of "1.23"equals 51.23 DegC."""
            var1 = ((adc_T) / 16384.0 - (self.dig_T1) / 1024.0) * (self.dig_T2)
            var2 = (((adc_T) / 131072.0 - (self.dig_T1) / 8192.0)  * ((adc_T) / 131072.0  - (self.dig_T1) / 8192.0)) * (self.dig_T3)
            self.t_fine = var1 + var2
            temperature = (var1 + var2) / 5120.0
            return temperature
            
        def compensate_pressure(self,adc_P):
            """Returns pressure in Pa as double. Output value of "6386.2"equals 96386.2 Pa = 963.862 hPa."""
            var1 = (self.t_fine / 2.0) - 64000.0
            var2 = var1 * var1 * (self.dig_P6) / 32768.0
            var2 = var2 + var1 * (self.dig_P5) * 2.0
            var2 = (var2 / 4.0) + ((self.dig_P4) * 65536.0) 
            var1 = ((self.dig_P3) * var1 * var1 / 524288.0  + (self.dig_P2) * var1) / 524288.0 
            var1 = (1.0 + var1 / 32768.0) * (self.dig_P1) 

            if var1 == 0.0: 
                return 0 # avoid exception caused by division by zero  

            pressure = 1048576.0 - adc_P
            pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1
            var1 = (self.dig_P9) * pressure * pressure / 2147483648.0
            var2 = pressure * (self.dig_P8) / 32768.0  
            pressure = pressure + (var1 + var2 + (self.dig_P7)) / 16.0

            return pressure; 

        def get_temperature_and_pressure(self):
            """Returns pressure in Pa as double. Output value of "6386.2"equals 96386.2 Pa = 963.862 hPa."""
            xlsb = self._read_byte(self.BMP280_TEMP_XLSB_REG)
            lsb =  self._read_byte(self.BMP280_TEMP_LSB_REG)
            msb =  self._read_byte(self.BMP280_TEMP_MSB_REG)

            adc_T = (msb << 12) | (lsb << 4) | (xlsb >> 4)
            temperature = self.compensate_temperature(adc_T)

            xlsb = self._read_byte(self.BMP280_PRESS_XLSB_REG)
            lsb =  self._read_byte(self.BMP280_PRESS_LSB_REG) 
            msb =  self._read_byte(self.BMP280_PRESS_MSB_REG) 

            adc_P = (msb << 12) | (lsb << 4) | (xlsb >> 4)
            pressure = self.compensate_pressure(adc_P)
            return temperature,pressure

    M的程doのset(('serv|bmp280',BMP180()))

    ans=M.serv.bmp280    
    return None if 'ans' not in locals() else ans
    

def time空module的程sec2str(args):
    #convert seconds to days-hours-minutes-seconds
    #args is float seconds
    arg=args
    ss=arg%60
    arg=(arg-ss)/60
    mm=arg%60
    arg=(arg-mm)/60
    hh=arg%24
    dd=(arg-hh)/24
    ans='%s day %s hour %s min %s sec'%(dd,hh,mm,ss)
    print(ans)    
    return None if 'ans' not in locals() else ans
    

def IP空address的程getのviaTerm(args):
    # get IP address info from terminal

    so=terminal的程doのrun(('ifconfig'))

    #print(so)
    ans=so    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程doのdispChars(args):
    # display some lines
    # adapted from Waveshare

    txt=args

    kobj=SSD1306的程drawのchars(txt)

    kobj.Display()    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程doのclear(args):
    # display some lines
    # adapted from Waveshare

    try:
        kobj=M.serv.SSD1306
    except:
        print(f'Creating new img server')
        kobj=SSD1306的程doのimgServ(None)

    kobj.Clear()    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程doのimgServ(args):
    # create an image serve for drawing and updating images and text

    import math
    import time

    import spidev as SPI
    import SSD1306

    from PIL import Image
    from PIL import ImageFont
    from PIL import ImageDraw


    # Raspberry Pi pin configuration:
    RST = 19
    DC = 16
    bus = 0
    device = 0

    # 128x32 display with hardware SPI:
    disp = SSD1306.SSD1306(rst=RST,dc=DC,spi=SPI.SpiDev(bus,device))

    # Initialize library.
    disp.begin()

    # Get display width and height.
    width = disp.width
    height = disp.height

    # Clear display.
    disp.clear()
    disp.display()

    # Create image buffer.
    # Make sure to create image with mode '1' for 1-bit color.
    image = Image.new('1', (width, height))

    # Get drawing object to draw on image.
    draw = ImageDraw.Draw(image)

    # Draw a black filled box to clear the image.
    draw.rectangle((0,0,width,height), outline=0, fill=0)
    if isinstance(K,alib):
        fontpath='simsun.ttf'
    else:
        fontpath=maop空path的程getのexpand('{MsdxDir}/misc/simsun.ttf')
    font=ImageFont.truetype(fontpath, 15)
    fontS=ImageFont.truetype(fontpath, 8)

    kobj=alib('',draw=draw,image=image,width=width,height=height,font=font,disp=disp,fontS=fontS)

    def Display(kobj=kobj):
        kobj.disp.image(kobj.image)
        kobj.disp.display()
    def Clear(kobj=kobj):
        #kobj.disp.clear()
        kobj.draw.rectangle((0,0,kobj.width,kobj.height), outline=0, fill=0)
        kobj.disp.image(kobj.image)
        kobj.disp.display()

    kobj['Display']=Display
    kobj['Clear']=Clear
    M的程doのset(('serv|SSD1306',kobj))

    ans=kobj    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程doのda(args):
    # digital to analog convert

    v=args #0 to 255

    try:
        serv=M.serv.adda
    except:
        print(f'Creating new adda server')
        serv=pcf8591的程servのadda(None)

    serv.Da(v)
    ans=v*3.3/255    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程doのad(args):
    # digital to analog convert

    add=args #0 to 3

    try:
        serv=M.serv.adda
    except:
        print(f'Creating new adda server')
        serv=pcf8591的程servのadda(None)

    ans=serv.Ad(add)    
    return None if 'ans' not in locals() else ans
    

def tibble的程doのsaveCsv(args):
    #save the tibble as a csv in sandbox

    df=args
    #print(f'df:{df}')
    loc=None
    if isinstance(df,alib):
        #[data][suffix][loc|custom location]
        suffix=df.suffix
        loc=df.loc if 'loc' in df else None
        df=df.data
    elif df==False or df is None:
        df=M.state.tibble
        suffix=''
    else:
        suffix=''
    #print(df)

    if loc is None:
        tfile=sandbox的程getのnewFile(f'-tibble{suffix}.csv')
    else:
        tfile=loc+suffix+'.csv'
    df.to_csv(tfile,encoding='utf-8-sig')
    terminal的程doのemphasize(f'Data saved to {tfile}')    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程doのdamon(args):
    # minion for adda monitoring


    dmem=alib('[name|adda]',beQuiet=True,standBy=0.1)
    damon=WalArt空minion的程getのminion(dmem)
    damon.reflex=.01

    try:
        serv=M.serv.adda
    except:
        print(f'Creating new adda server')
        serv=pcf8591的程servのadda(None)


    damon.mem=dmem
    dmem['daVal']=[1]
    dmem['daLen']=len(dmem.daVal)
    dmem['lData']=[]
    dmem['lTime']=[]
    dmem['serv']=serv
    dmem['i']=0
    dmem['tStart']=time.time()

    def Dtask(d=damon):
        i=d.mem.i%d.mem.daLen
        dav=d.mem.daVal[i]
        v=d.mem.serv.Ad(0)
        d.mem.serv.Da(dav)
        d.mem.lData.append(v)
        d.mem.lTime.append(time.time())
        d.mem.i+=1

    def Dset(l,d=damon):
        # set a list to DA
        d.mem['daLen']=len(l)
        d.mem['daVal']=l

    damon.mem['Dset']=Dset

    damon.DontStop([Dtask])

    ans=damon    
    return None if 'ans' not in locals() else ans
    

def list的程getのfetch(args):
    # return the last x items from the list

    opt=args #[l][last][default]

    if len(opt.l)>=opt.last:
        ans=opt.l[-opt.last:-1]
    else:
        ans=[opt.default]*(opt.last-len(opt.l))+opt.l    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程drawのplot(args):
    # plot a curve

    opt=args # [x][y][ylim]

    ylim=(0,3.3)

    import numpy as np
    x=opt.x
    y=opt.y

    try:
        kobj=M.serv.SSD1306
    except:
        print(f'Creating new img server')
        kobj=SSD1306的程doのimgServ(None)

    # Draw some shapes.
    # First define some constants to allow easy resizing of shapes.
    tl=64, 16
    w,h=60,40

    kobj.draw.rectangle((tl[0],tl[1],tl[0]+w,tl[1]+h), outline=255, fill=0)
    xs=np.linspace(x[0],x[-1],w)
    ys=np.interp(xs,x,y)

    Kbt=affine空transformation的程getのkbtrans(alib('',a1=ylim[0],a2=ylim[1],b1=0,b2=h))
    draw=kobj.draw
    #print(ys)
    for i,y in enumerate(ys):
        ix=tl[0]+i
        iy=tl[1]+h-int(Kbt(y))
        draw.point((ix,iy),fill=255)

    ans=kobj    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程drawのchars(args):
    # draw chars on the canvas, no display yet
    # return the drawing serv
    txt=args

    if isinstance(txt,str):
        lines=txt.split('\n')
    else:
        lines=txt

    try:
        kobj=M.serv.SSD1306
    except:
        print(f'Creating new img server')
        kobj=SSD1306的程doのimgServ(None)

    # Draw some shapes.
    # First define some constants to allow easy resizing of shapes.
    padding = 1
    top = padding
    x = padding

    kobj.draw.rectangle((0,0,kobj.width,kobj.height), outline=0, fill=0)

    font=kobj.font
    # Write two lines of text.
    kobj.draw.text((x, top), lines[0], font=font, fill=255)
    kobj.draw.text((x, top+15), lines[1], font=font, fill=255)
    kobj.draw.text((x, top+30), lines[2], font=font, fill=255)
    kobj.draw.text((x, top+45), lines[3], font=font, fill=255)

    ans=kobj    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程doのgetTP(args):
    # get temperature and pressure

    try:
        kobj=M.serv.bmp280
    except:
        print(f'Creating new TP server')
        kobj=Pioneer600的程servのbmp280(None)

    ans=kobj.get_temperature_and_pressure()    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程doのcheckKey(args):
    # just check the current state of the 5-way key, no blocking

    import RPi.GPIO as GPIO
    import smbus
    import time
    address = 0x20
    bus = smbus.SMBus(1)

    bus.write_byte(address,0x0F|bus.read_byte(address))
    value = bus.read_byte(address) | 0xF0
    if value != 0xFF:
        if (value | 0xFE) != 0xFF:
            ans='L'
        elif (value | 0xFD) != 0xFF:
            ans='U'
        elif (value | 0xFB) != 0xFF:
            ans='D'
        else :
            ans='R'
    else:
        ans=None    
    return None if 'ans' not in locals() else ans
    

def time空module的程getのuptime(args):
    # return the time passed since system boot

    import psutil

    tStart=psutil.boot_time()

    tDur=time.time()-tStart

    ans=time空module的程sec2str(tDur)

    #print(ans)    
    return None if 'ans' not in locals() else ans
    

def raspberry空pi的程getのramInfo(args):
    #return ram Info [free][total][used]
    import os

    p = os.popen('free')
    i = 0
    while 1:
        i = i + 1
        line = p.readline()
        if i==2:
            res=(line.split()[1:4])
            break
    ans=alib('[class|ram info]',total=res[0],used=res[1],free=res[2])
    print(ans)    
    return None if 'ans' not in locals() else ans
    

def raspberry空pi的程getのipAddress(args):
    #return the available ip address

    so=IP空address的程getのviaTerm(None)

    lines=so.splitlines()
    ans=alib('[class|IP address info]')
    for i,l in enumerate(lines):
        if 'inet ' in l:
            idx=l.index('inet ')
            tl=lines[i-1]
            idxn=l.index('netmask')
            ans[tl[:tl.index(':')]]=l[idx+5:idxn-2]

    print(ans)    
    return None if 'ans' not in locals() else ans
    

def raspberry空pi的程getのdiskInfo(args):
    #return disk usage, in str
    # from https://shumeipai.nxez.com/2014/10/04/get-raspberry-the-current-status-and-data.html

    import os

    p = os.popen("df -h /")
    i = 0
    while 1:
        i = i +1
        line = p.readline()
        if i==2:
            res=(line.split()[1:5])
            break

    ans=alib('[class|disk info]',total=res[0],used=res[1],free=res[2], loadfactor=res[3])
    print(ans)    
    return None if 'ans' not in locals() else ans
    

def raspberry空pi的程getのcpuTemp(args):
    #return CPU temperature
    # from https://shumeipai.nxez.com/2014/10/04/get-raspberry-the-current-status-and-data.html

    import os
    res = os.popen('vcgencmd measure_temp').readline()
    ans=res.replace("temp=","").replace("'C\n","")

    print(f'Temperature is {ans}')    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程loopのtext(args):
    # display long text using word wrap

    opt=args #[text][#title]
    if 'title' not in opt:
        opt['title']='Text view'

    CheckKey=Pioneer600的程doのcheckKey
    DispChar=SSD1306的程doのdispChars
    import textwrap

    k=CheckKey(None)
    i=0
    lines=textwrap.wrap(opt.text,width=16)
    nl=len(lines)

    while k!='L':
        if i==0:
            l1=''
        else:
            l1=lines[i-1]
        l2=lines[i]
        if i==nl-1:
            l3=''
        else:
            l3=lines[i+1]
        DispChar([opt.title,l1,l2,l3])
        k=CheckKey(None)
        if k=='U':
            i-=1
        elif k=='D':
            i+=1
        if i>nl-1:
            i=nl-1
        elif i<0:
            i=0
        time.sleep(.1)

    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程doのbeep(args):
    # beep for x seconds

    x=args

    import RPi.GPIO as GPIO
    import smbus
    import time

    address = 0x20
    bus = smbus.SMBus(1)

    bus.write_byte(address,0x7F&bus.read_byte(address))
    time.sleep(x)

    bus.write_byte(address,0x80|bus.read_byte(address))    
    return None if 'ans' not in locals() else ans
    

def alib的程getのleaves(args):
    #return an alib that has all the input alib leaf node name as keys and path as values, if duplicate leaf name, the value is a list
    #thus the returned alib has only one layer

    a=args #the input data tree

    leaves=alib()

    nexts=[a]
    paths=dict()
    paths[id(a)]=[] #record the path, valid for this python session

    while len(nexts)>0:
        r=nexts.pop() #r should always be an alib
        path=paths[id(r)] #path of this alib
        for k in r:
            if isinstance(r[k],alib) and id(r[k]) not in paths:
                nexts.append(r[k])
                paths[id(r[k])]=path+[k]
            else: #then r[k] is a leaf node, record it in leaves
                rkpath='|'.join(path+[k])
                if k in leaves and isinstance(leaves[k],list):
                    leaves[k].append(rkpath)
                elif k in leaves: #then the second entry
                    leaves[k]=[leaves[k],rkpath]
                else: #the first entry of leaves[k]
                    leaves[k]=rkpath

    ans=leaves    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程drawのmenu(args):
    # draw the menu interface

    opt=args #[title][cur][menu|list of menu items][T|translation function]
    title,cur,menu,T=opt.ToList(['title','cur','menu','T'])
    lines=['::'+T(title)]

    if cur==0:
        lines.append('  ')
    else:
        lines.append('  '+T(menu[cur-1]))
    lines.append('--'+T(menu[cur]))
    if cur==len(menu)-1:
        lines.append('  ')
    else:
        lines.append('  '+T(menu[cur+1]))

    SSD1306的程doのdispChars(lines)    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程doのgetKey(args):
    # wait and return a position key

    import RPi.GPIO as GPIO
    import smbus
    import time
    address = 0x20
    bus = smbus.SMBus(1)

    ans=None

    while True:
        bus.write_byte(address,0x0F|bus.read_byte(address))
        value = bus.read_byte(address) | 0xF0
        if value != 0xFF:
            if (value | 0xFE) != 0xFF:
                ans='L'
            elif (value | 0xFD) != 0xFF:
                ans='U'
            elif (value | 0xFB) != 0xFF:
                ans='D'
            else :
                ans='R'
            while value != 0xFF:
                bus.write_byte(address,0x0F|bus.read_byte(address))
                value = bus.read_byte(address) | 0xF0
                time.sleep(0.01)
            if ans:
                break
            time.sleep(0.1)    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程testのaddaspd(args):
    # check and display adda speed

    CheckKey=Pioneer600的程doのcheckKey

    DispChar=SSD1306的程doのdispChars

    sf='soft ADDA speed\n {:.2f} sps\nD{:.2f}V A0{:.2f}V\nLeft to exit'

    v=128
    Ad=pcf8591的程doのad
    Da=pcf8591的程doのda

    k=CheckKey(None)
    dur=0.1
    while k!='L':
        tic=time.time()
        a=Ad(0)
        d=Da(v)
        if k=='U':
            v+=2
        elif k=='D':
            v-=2
        if v>255:
            v=255
        elif v<0:
            v=0
        dur=time.time()-tic
        DispChar(sf.format(1/dur,d,a))
        time.sleep(0.03)
        k=CheckKey(None)

    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程testのadda(args):
    #test adda
    CheckKey=Pioneer600的程doのcheckKey
    DispChar=SSD1306的程doのdispChars
    Ad=pcf8591的程doのad
    Da=pcf8591的程doのda

    sf='AD/DA test\n0:{a0:.2f} 1: {a1:.2f}\n2:{a2:.2f} 3: {a3:.2f}\nD: {v}, ←:exit'
    k=CheckKey(None)
    v=128

    while k!='L':
        al=alib('',a0=Ad(0),a1=Ad(1),a2=Ad(2),a3=Ad(3),v=v)
        Da(v)
        DispChar(sf.format(**al))
        k=CheckKey(None)
        if k=='U':
            v+=20
        elif k=='D':
            v-=20
        if v>255:
            v=255
        elif v<0:
            v=0
        time.sleep(.1)

    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程loopのcycleTest(args):
    # minion and interface for adda monitoring

    # check the adda signal

    CheckKey=Pioneer600的程doのcheckKey

    cpts=5 # points in half cycle

    DrawChar=SSD1306的程drawのchars
    Plot=SSD1306的程drawのplot
    Fetch=list的程getのfetch
    lopt=alib('[l]',default=0,last=200)
    dopt=alib('',x=[0,2],y=[0,1])
    v=128

    sf='ADDA Plot test\nD{:.2f}V\nA0{:.2f}V\n←exit'
    k=CheckKey(None)

    try:
        mon=M.aMinion.adda
    except:
        mon=pcf8591的程doのdamon(None)
    time.sleep(.1)
    iter=0
    ndisp=0
    msg=''
    while k!='L':
        if k=='U':
            v+=5
        elif k=='D':
            v-=5
        if v>255:
            v=255
        elif v<0:
            v=0
        if k=='U' or k=='D':
            mon.mem.Dset([v]*cpts+[0]*cpts)
        elif k=='R':
            import pandas as pd
            df=pd.DataFrame(dict(time=mon.mem.lTime,voltage=mon.mem.lData))
            tibble的程doのsaveCsv(alib('[suffix|-adda]',data=df))
            print(f'{len(df)} points saved')
            msg=' saved'
        lopt['l']=mon.mem.lTime
        lts=Fetch(lopt)
        lopt['l']=mon.mem.lData
        lds=Fetch(lopt)
        kobj=DrawChar(sf.format(v*3.3/255,lds[-1]))
        dopt['x']=lts
        dopt['y']=lds
        Plot(dopt)
        kobj.draw.text((66, 56), f'{ndisp} cycles', font=kobj.fontS, fill=255)
        kobj.Display()
        if iter%20==5:
            ndisp=len(mon.mem.lTime)/2/cpts
            msg=''
        time.sleep(0.03)
        iter+=1
        k=CheckKey(None)
    mon.beQuiet=False
    mon.Bye()
    M.aMinion.pop('adda')
    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def pcf8591的程loopのaddamon(args):
    # minion and interface for adda monitoring

    # check the adda signal

    CheckKey=Pioneer600的程doのcheckKey

    DrawChar=SSD1306的程drawのchars
    Plot=SSD1306的程drawのplot
    Fetch=list的程getのfetch
    lopt=alib('[l]',default=0,last=200)
    dopt=alib('',x=[0,2],y=[0,1])
    v=128

    sf='ADDA Plot test\nD{:.2f}V\nA0{:.2f}V\n←exit'
    k=CheckKey(None)

    try:
        mon=M.aMinion.adda
    except:
        mon=pcf8591的程doのdamon(None)
    time.sleep(.1)
    iter=0
    lenTime=0
    msg=''
    while k!='L':
        if k=='U':
            v+=5
        elif k=='D':
            v-=5
        if v>255:
            v=255
        elif v<0:
            v=0
        if k=='U' or k=='D':
            mon.mem.Dset([v])
        elif k=='R':
            import pandas as pd
            df=pd.DataFrame(dict(time=mon.mem.lTime,voltage=mon.mem.lData))
            tibble的程doのsaveCsv(alib('[suffix|-adda]',data=df))
            print(f'{len(df)} points saved')
            msg=' saved'
        lopt['l']=mon.mem.lTime
        lts=Fetch(lopt)
        lopt['l']=mon.mem.lData
        lds=Fetch(lopt)
        kobj=DrawChar(sf.format(v*3.3/255,lds[-1]))
        dopt['x']=lts
        dopt['y']=lds
        Plot(dopt)
        kobj.draw.text((66, 56), f'{lenTime} samples', font=kobj.fontS, fill=255)
        kobj.Display()
        if iter%20==5:
            lenTime=len(mon.mem.lTime)
            msg=''
        time.sleep(0.03)
        iter+=1
        k=CheckKey(None)
    mon.Bye()
    M.aMinion.pop('adda')
    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def SSD1306的程testのframeRate(args):
    # check and display frame rate

    CheckKey=Pioneer600的程doのcheckKey

    DispChar=SSD1306的程doのdispChars

    sf='Frame test\ncurrent frame \nrate:{:.2f} fps\nLeft to exit'
    k=CheckKey(None)
    dur=0.1
    while k!='L':
        tic=time.time()
        DispChar(sf.format(1/dur))
        dur=time.time()-tic
        time.sleep(0.03)
        k=CheckKey(None)

    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程testのtp(args):
    #test temperature and pressure

    # check and display frame rate

    CheckKey=Pioneer600的程doのcheckKey

    DispChar=SSD1306的程doのdispChars

    sf='温度压强传感\n{:.2f} ℃\n{:.3f} kPa\n{:.1f} s, ←:exit'
    k=CheckKey(None)
    TP=Pioneer600的程doのgetTP

    start=time.time()
    while k!='L':
        #tic=time.time()
        t,p=TP(None)
        dur=time.time()-start
        DispChar(sf.format(t,p/1000,dur))
        k=CheckKey(None)
        time.sleep(.1)
        #dur=time.time()-tic

    SSD1306的程doのclear(None)    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程testのsysinfo(args):
    # display system info in a long text, test loop text

    ip=raspberry空pi的程getのipAddress(None)
    disk=raspberry空pi的程getのdiskInfo(None)
    ram=raspberry空pi的程getのramInfo(None)
    cput=raspberry空pi的程getのcpuTemp(None)
    uptime=time空module的程getのuptime(None)

    text=f'{ip}\n======\n{disk}\n======\n{ram}\n=====\nCPU: {cput}℃\nUptime: {uptime}'

    SSD1306的程loopのtext(alib('[title|SYSTEM INFO]',text=text))    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程testのbeep(args):
    # test beep
    k=None
    msg='Beeper:\nPress a key\non the joystick, \nleft to exit'
    print(msg)
    SSD1306的程doのdispChars(msg)
    while k!='L':
        k=Pioneer600的程doのgetKey(None)
        print(k)
        Pioneer600的程doのbeep(.1)    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程runのmenu(args):
    # run menu interface

    stem=args #application model

    if '#translate' in stem:
        trans=stem.pop('#translate')
        def T(s,al=trans):
            return s if s not in al else al[s]
    else:
        T=lambda s:s
    if '#aFunc' in stem:
        aFunc=stem.pop('#aFunc')
        aLeaf=alib的程getのleaves(stem)
        for l in aLeaf:
            if l in aFunc:
                stem.setValue(aLeaf[l],aFunc[l])

    GetKey=Pioneer600的程doのgetKey
    Draw=SSD1306的程drawのmenu

    lPath=[] # a list of current path
    aMenu=stem #alib of current menu
    lMenu=list(aMenu.keys())
    sTitle=''
    lCur=[0] # current index
    on=True
    while on:
        Draw(alib('',menu=lMenu,title=sTitle,cur=lCur[-1],T=T))
        k=GetKey(None)
        if k=='U':
            if lCur[-1]>0:
                lCur[-1]-=1
        elif k=='D':
            if lCur[-1]<len(lMenu)-1:
                lCur[-1]+=1
        elif k=='L':
            if lPath:
                lPath.pop()
                aMenu=stem.getValue('|'.join(lPath)) if lPath else stem
                lMenu=list(aMenu.keys())
                sTitle=lPath[-1] if lPath else ''
                lCur.pop()
        else: #k=='R'
            sCur=lMenu[lCur[-1]]
            print(f'Type of {sCur} is {type(aMenu[sCur])}')
            if isinstance(aMenu[sCur],alib):
                lPath.append(sCur)
                aMenu=aMenu[sCur]
                lCur.append(0)
                lMenu=list(aMenu.keys())
                sTitle=sCur
            elif callable(aMenu[sCur]):
                print(f'Executing')
                aMenu[sCur](None)
            else:
                on=False
                print(f'Exiting')
        time.sleep(.1)
    SSD1306的程doのclear(None)
    if isinstance(K,alib):
        exit() #exit in standalone mode    
    return None if 'ans' not in locals() else ans
    

def Pioneer600的程testのmenu(args):
    # test menu interface

    stem=alib('''[systest|[ADDA]
    [frameRate][temperature][ADDAspd][sysinfo]]
    [usertest|[beep][ADDAplt][cycleTest]]
    [exit|#exit]''')

    stem['#aFunc']=alib('',ADDA=pcf8591的程testのadda,
    frameRate=SSD1306的程testのframeRate,
    temperature=Pioneer600的程testのtp,
    ADDAspd=pcf8591的程testのaddaspd,
    ADDAplt=pcf8591的程loopのaddamon,
    beep=Pioneer600的程testのbeep,
    sysinfo=Pioneer600的程testのsysinfo,
    cycleTest=pcf8591的程loopのcycleTest)

    stem['#translate']=alib('''[|主菜单]
    [systest|系统自检]
    [ADDA|数模转换]
    [frameRate|测试帧率]
    [temperature|温度压力传感]
    [usertest|用户程序]
    [exit|退出]
    [beep|蜂鸣器]
    [ADDAspd|数模采样速率]
    [sysinfo|系统信息]
    [ADDAplt|数模采样画图]
    [cycleTest|循环电压测试]''')

    Pioneer600的程runのmenu(stem)    
    return None if 'ans' not in locals() else ans
    

main=Pioneer600的程testのmenu

if __name__ == '__main__':
    import sys
    print(sys.argv)
    import os
    print(os.getcwd())
    if len(sys.argv)>1:
        os.chdir(sys.argv[1])
        print(f'Changed to {os.getcwd()}')

    if 'show' in main.__name__:
        from PyQt5.QtWidgets import QApplication
        app = QApplication(sys.argv)
        main(None)
        app.exec_()
    else:
        main(None)